﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using OpenTap;
// ReSharper disable LocalizableElement

namespace OpenTap.Plugins
{
    // Interface needed for helping the mocking of FTP features
    public interface IFtp
    {
        /// <summary>
        ///     Fetches file from remote device via FTP
        /// </summary>
        /// <param name="ipAddress">Ip address from which file is fetched</param>
        /// <param name="userName">User name for login</param>
        /// <param name="passWord">Password for login</param>
        /// <param name="passive">Specifies passive or active mode for this FTP session. True is passive</param>
        /// <param name="remoteFile">Name of the file at remote location</param>
        /// <param name="localFile">File name at local hard drive</param>
        /// <param name="ftpTimeoutS">Time out for file transfer in secs</param>
        void GetFile(string ipAddress, string userName, string passWord, bool passive, string remoteFile,
            string localFile, int ftpTimeoutS);

        /// <summary>
        ///     Fetches file from remote device via FTP to login to anonymously.
        /// </summary>
        /// <param name="ipAddress">Ip address from which file is fethed</param>
        /// <param name="passive">Specifies passive or active mode for this FTP session. True is passive</param>
        /// <param name="remoteFile">Name of the file at remote location</param>
        /// <param name="localFile">File name at local hard drive</param>
        /// <param name="ftpTimeoutS">Time out for file transfer in secs</param>
        void GetFile(string ipAddress, bool passive, string remoteFile,
            string localFile, int ftpTimeoutS);

        /// <summary>
        ///     Returns directory contents via FTP to login to anonymously.
        /// </summary>
        /// <param name="ipAddress">Ip address from which directory is fetched</param>
        /// <param name="userName">User name for login</param>
        /// <param name="passWord">Password for login</param>
        /// <param name="passive">Specifies passive or active mode for this FTP session. True is passive</param>
        /// <param name="directory">Name of the directory at remote location</param>
        /// <param name="ftpTimeoutS">Time out for directory listing in secs</param>
        /// <returns>Directory list</returns>
        List<string> DirList(string ipAddress, string userName, string passWord, bool passive, string directory,
            int ftpTimeoutS);

        /// <summary>
        ///     Returns directory contents via FTP
        /// </summary>
        /// <param name="ipAddress">Ip address from which directory is fetched</param>
        /// <param name="passive">Specifies passive or active mode for this FTP session. True is passive</param>
        /// <param name="directory">Name of the directory at remote location</param>
        /// <param name="ftpTimeoutS">Time out for directory listing in secs</param>
        /// <returns>Directory list</returns>
        List<string> DirList(string ipAddress, bool passive, string directory, int ftpTimeoutS);

        /// <summary>
        ///     Upload file to FTP server.
        /// </summary>
        /// <param name="ipAddress">remote ip address</param>
        /// <param name="userName">User name for login</param>
        /// <param name="passWord">Password for login</param>
        /// <param name="passive">Specifies passive or active mode for this FTP session. True is passive</param>
        /// <param name="remoteFile">Name of the file at remote location</param>
        /// <param name="localFile">File name at local hard drive</param>
        void SendFile(string ipAddress, string userName, string passWord, bool passive, string remoteFile,
            string localFile);

        /// <summary>
        ///     Check if directory exist.
        /// </summary>
        /// <param name="ipAddress">remote ip address</param>
        /// <param name="userName">User name for login</param>
        /// <param name="passWord">Password for login</param>
        /// <param name="directory">Name of the folder</param>
        bool DirectoryExists(string ipAddress, string userName, string passWord, string directory);

        /// <summary>
        ///     Create directory.
        /// </summary>
        /// <param name="ipAddress">remote ip address</param>
        /// <param name="userName">User name for login</param>
        /// <param name="passWord">Password for login</param>
        /// <param name="directory">Name of the folder</param>
        void CreateDirectory(string ipAddress, string userName, string passWord, string directory);
    }

    public class Ftp : IFtp
    {
        private readonly TraceSource FtpLog = Log.CreateSource("Ftp");

        public void GetFile(string ipAddress, string userName, string passWord, bool passive, string remoteFile,
            string localFile, int ftpTimeoutS)
        {
            if (string.IsNullOrWhiteSpace(ipAddress))
                throw new ArgumentException("Value cannot be null or whitespace.", "ipAddress");
            if (string.IsNullOrWhiteSpace(userName))
                throw new ArgumentException("Value cannot be null or whitespace.", "remoteFile");
            if (string.IsNullOrWhiteSpace(localFile))
                throw new ArgumentException("Value cannot be null or whitespace.", "localFile");
            if (ftpTimeoutS < 0) throw new ArgumentOutOfRangeException("ftpTimeoutS");

            const int bufferSize = 2048;

            try
            {
                FtpLog.Info("FTP Get File ftp://" + ipAddress + "/" + remoteFile + " to " + localFile);

                var ftpRequest = (FtpWebRequest) WebRequest.Create("ftp://" + ipAddress + "/" + remoteFile);

                ftpRequest.Credentials = new NetworkCredential(userName, passWord);
                ftpRequest.UseBinary = true;
                ftpRequest.UsePassive = passive;
                ftpRequest.Proxy = null;
                ftpRequest.KeepAlive = true;
                ftpRequest.Timeout = ftpTimeoutS * 1000;
                ftpRequest.Method = WebRequestMethods.Ftp.DownloadFile;

                using (var ftpResponse = (FtpWebResponse) ftpRequest.GetResponse())
                {
                    // Get the FTP server's response stream
                    using (var ftpStream = ftpResponse.GetResponseStream())
                    {
                        if (ftpStream != null)
                        {
                            // Create local file
                            using (var localFileStream = new FileStream(localFile, FileMode.Create))
                            {
                                // Buffer for the downloaded data
                                var buffer = new byte[bufferSize];
                                // Read remote data to local file
                                var bytesRead = ftpStream.Read(buffer, 0, bufferSize);
                                while (bytesRead > 0)
                                {
                                    localFileStream.Write(buffer, 0, bytesRead);
                                    bytesRead = ftpStream.Read(buffer, 0, bufferSize);
                                }
                            }
                        }
                        else
                        {
                            throw new ApplicationException("Error: Can not open FTP stream reader!");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                FtpLog.Error(ex);
                var error = "Error: Ftp Get File failed:" + ex;
                throw new ApplicationException(error);
            }
        }

        public List<string> DirList(string ipAddress, string userName, string passWord, bool passive, string directory,
            int ftpTimeoutS)
        {
            if (ipAddress == null) throw new ArgumentNullException("ipAddress");
            if (ftpTimeoutS < 0) throw new ArgumentOutOfRangeException("ftpTimeoutS");

            var directoryList = new List<string>();

            try
            {
                FtpLog.Info("FTP Dir List from ftp://" + ipAddress + directory);

                var ftpRequest = (FtpWebRequest) WebRequest.Create("ftp://" + ipAddress + directory);

                // Anonymous logon
                ftpRequest.Credentials = new NetworkCredential(userName, passWord);
                ftpRequest.UseBinary = true;
                ftpRequest.UsePassive = passive;
                ftpRequest.Proxy = null;
                ftpRequest.KeepAlive = true;
                ftpRequest.Timeout = ftpTimeoutS * 1000;

                ftpRequest.Method = WebRequestMethods.Ftp.ListDirectory;
                using (var ftpResponse = (FtpWebResponse) ftpRequest.GetResponse())
                {
                    // Get the FTP server's response stream
                    using (var ftpStream = ftpResponse.GetResponseStream())
                    {
                        if (ftpStream != null)
                        {
                            // Read response and add to directory list
                            using (var streamReader = new StreamReader(ftpStream))
                            {
                                while (streamReader.Peek() != -1)
                                    directoryList.Add(streamReader.ReadLine());
                            }
                        }
                        else
                        {
                            throw new ApplicationException("Error: Can not open FTP stream reader!");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                FtpLog.Error(ex);
                var error = "Error: FTP Dir List failed: " + ex;
                throw new ApplicationException(error);
            }

            return directoryList;
        }

        public void GetFile(string ipAddress, bool passive, string remoteFile, string localFile, int ftpTimeoutS)
        {
            GetFile(ipAddress, "anonymous", "user", passive, remoteFile, localFile, ftpTimeoutS);
        }

        public List<string> DirList(string ipAddress, bool passive, string directory, int ftpTimeoutS)
        {
            return DirList(ipAddress, "anonymous", "user", passive, directory, ftpTimeoutS);
        }

        public void SendFile(string ipAddress, string userName, string passWord, bool passive, string remoteFile,
            string localFile)
        {
            using (
                var client = new WebClient
                {
                    Proxy = null,
                    Credentials = new NetworkCredential(userName, passWord)
                })
            {
                client.UploadFile("ftp://" + ipAddress + "/" + remoteFile, "STOR", localFile);
                FtpLog.Info("Upload File Complete");
            }
        }

        public bool DirectoryExists(string ipAddress, string userName, string passWord, string directory)
        {
            var status = true;
            try
            {
                var request = (FtpWebRequest)WebRequest.Create("ftp://" + ipAddress + "/" + directory +"/");
                request.Proxy = null;
                request.Credentials = new NetworkCredential(userName, passWord);
                request.Method = WebRequestMethods.Ftp.ListDirectory;

                // Just check if exception is thrown. The returned response is not actually used for anything
                using (request.GetResponse()){}
            }
            catch (WebException ex)
            {
                if (ex.Response == null) return true;
                var response = (FtpWebResponse) ex.Response;
                if (response.StatusCode == FtpStatusCode.ActionNotTakenFileUnavailable)
                    status = false;
            }
            return status;
        }

        public void CreateDirectory(string ipAddress, string userName, string passWord, string directory)
        {
            var request = (FtpWebRequest) WebRequest.Create("ftp://" + ipAddress + "/" + directory);
            request.Proxy = null;
            request.Credentials = new NetworkCredential(userName, passWord);
            request.Method = WebRequestMethods.Ftp.MakeDirectory;
            //check if exception is thrown, can catch that if needed
            request.GetResponse();
        }
    }
}